﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using OxyPlot;

namespace DataInterpolator.Model
{
   public class Well_ExtrapolatedData
   {
      public Well_ExtrapolatedData()
      {
         Data = new List<DataPoint>();
         RefValues = new Dictionary<string, ZeroRef>();
      }

      public List<DataPoint> Data
      {
         get; private set;
      }

      public Dictionary<string, ZeroRef> RefValues
      {
         get; private set;
      }

      private double FindFirstValue(IList<double> values)
      {
         return values.First((d) =>
         {
            return !double.IsNaN(d);
         });
      }

      public void Calc(Well well)
      {
         RefValues.Clear();
         Data.Clear();

         //Add the existing points
         for (int i = 0; i < well.LogData.Count; i++)
         {
            Data.Add(new DataPoint(well.LogData[i].Time, well.LogData[i].Depth));
         }

         RefValues["LogStart"] = new ZeroRef(well.LogData[0].Time, well.LogData[0].Depth);

         double refDepth = 0.0;
         double refTime = 0.0;
         //Calc first the refTime and depth of KB
         double vel = FindFirstValue(well.LogData.Velocity);

         if (vel <= 0.0)
            throw new Exception("Unable to find first velocity value for well: " + well.Title);

         vel /= 1000.0; //Convert to ft/ms
         vel /= 2.0; //Compensate for the two-way distance calculation

         double firstDepth = FindFirstValue(well.LogData.Depth);

         if (firstDepth <= 0.0)
            throw new Exception();

         double firstTime = FindFirstValue(well.LogData.Time);

         refDepth = 0.0;
         refTime = -firstDepth / vel;

         RefValues["KB"] = new ZeroRef(refTime, refDepth);

         double time = 0.0;

         for (time = firstTime - well.SampleRate; time >= refTime; time -= well.SampleRate)
         {
            double dt = time - Data[0].X;
            double depth = Data[0].Y + dt * vel;

            if (depth <= refDepth)
               break;

            Data.Insert(0, new DataPoint(time, depth));
         }

         //Always insert the target value
         Data.Insert(0, new DataPoint(refTime, refDepth));

         if (well.SeismicDatum >= well.KB && well.SeismicVelocity > 0.0)
         {
            //Calc diff between KB and seismic datum
            refDepth = well.KB - well.SeismicDatum;
            refTime += refDepth / (well.SeismicVelocity / 2000.0);

            RefValues["SeismicDatum"] = new ZeroRef(refTime, refDepth);

            for (; time >= refTime; time -= well.SampleRate)
            {
               double dt = time - Data[0].X;
               double depth = Data[0].Y + dt * (well.SeismicVelocity / 2000.0);

               if (depth <= refDepth)
                  break;

               Data.Insert(0, new DataPoint(time, depth));
            }

            //Always insert the target value
            Data.Insert(0, new DataPoint(refTime, refDepth));
         }
         else
         {
            RefValues["SeismicDatum"] = RefValues["KB"];
         }

         //Finally, interpolate the tops ref values
         foreach (var top in well.Tops.Formations)
         {
            double d = top.Depth;
            double t = top.Time * 1000.0;

            //Now find the two points surrounding the depth
            int startIdx = Data.FindLastIndex((p) => {
               return p.Y < d;
            });

            if (startIdx + 1 < Data.Count)
            {
               var left = Data[startIdx];
               var right = Data[startIdx + 1];

               //Linear interp between given values
               t = left.X + (right.X - left.X) * ((d - left.Y) / (right.Y - left.Y));
            }

            RefValues[top.Name] = new ZeroRef(t, d);
         }
      }
   }

   public class ZeroRef
   {
      public ZeroRef(double time, double depth)
      {
         Time = time;
         Depth = depth;
      }

      public double Time
      {
         get; set;
      }

      public double Depth
      {
         get; set;
      }
   }
}
